import numpy as np
from collections import deque

import call_counter
import damped_newton
import extend_powell_singular
import fletcher_freeman
import gm_stable_newton
import trigonometric
import wood
from optimizer_util import show_result, wolfe_line_search, create_gll_wolfe


def run_benchmark(name, method_name, ls_name, create, optimize, ls, n):
    print("%s, %s, %s" % (name, method_name, ls_name))
    x0, f0, g0, h0 = create(n) if (n is not None) else create()
    f, g, h, fc, gc, hc = call_counter.wrap(f0, g0, h0)
    x, it_n, it_ls, msg = optimize(f, g, h, x0, 400, 1e-8, ls)
    show_result(f0, x, it_n, it_ls, msg, fc, gc, hc)
    print("||g*||= %s" % np.sqrt(np.sum(g0(x) ** 2)))
    print("||G*||= %s" % np.linalg.norm(h0(x), ord=2))
    print("min eigen val of G*= %s" % np.min(np.linalg.eigvalsh(h0(x))))
    print("|G*|= %s" % np.linalg.det(h0(x)))
    print()


def main():
    # (name, function)
    methods = [
        ("Damped Newton", damped_newton.optimize),
        ("Gill-Murray Stable Newton", gm_stable_newton.optimize),
        ("Fletcher-Freeman", fletcher_freeman.optimize),
    ]
    # (name, function, #has argument)
    ls_methods = [
        ("Wolfe", wolfe_line_search, False),
        ("GLL with Wolfe condition, M=10", create_gll_wolfe, True)
    ]
    # (name, function, #variable dimensionality)
    functions = [
        ("Wood", wood.create, False),
        ("Extended Powell singular", extend_powell_singular.create, True),
        ("Trigonometric", trigonometric.create, True)
    ]
    dimensions = [20, 40, 60, 80, 100]
    for f_name, f_create, var_dim in functions:
        f_dim = dimensions if var_dim else [None]
        for n in f_dim:
            for m_name, m_func in methods:
                for ls_name, ls_func, ls_has_arg in ls_methods:
                    full_name = f_name + " " + str(n) if var_dim else f_name
                    lsf = ls_func(11, deque([])) if ls_has_arg else ls_func
                    run_benchmark(full_name, m_name, ls_name, f_create, m_func, lsf, n)


if __name__ == '__main__':
    main()
